<?php
/**
 * php GEM Project
 * 
 * LICENSE
 * 
 * PHP System Architecture Enabling Rapid Application Deployment and Scalability
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @copyright Copyright (C) 2010 Zeletron LLC (http://zeletron.com)
 * @license http://gem.zeletron.com/license
 */

/*
 * GEM/Model/Domain/Object.php
 * 
 * This is an abstract domain object class. Extend this
 * with your domain objects to make use of the domain object
 * model infrastructure.
 * 
 * @author David Joly
 * @link http://zeletron.com
 * 
 * @category GEM
 * @package GEM_Model
 * @subpackage GEM_Model_Domain
 * 
 */

abstract class GEM_Model_Domain_Object
{	
	/**
	 * Some object properties may contain values that either cannot,
	 * or should not, be output to JSON. To prevent the toJson() method
	 * from returning a property, that property can be included in this
	 * array. Extending class may add to the array during instantiation
	 * using _jsonExclude().
	 * 
	 * @var array
	 */
	private $_safemodeExclude = array('_safemodeExclude','_changedProperties');
	
	/**
	 * Stores the object's corresponding database table id
	 * 
	 * @var int
	 */
	protected $_id = -1;
	
	/**
	 * Array of changed properties.
	 * 
	 * Allows for "lazy updates". In your extending classes,
	 * track property changes by adding changed properties to this
	 * array using your setter methods. You can then examine this 
	 * array in your mapper class to limit updates to only
	 * changed fields.
	 * 
	 * @var array
	 */
	protected $_changedProperties = array();
	
	/**
	 * Constructor 
	 * 
	 * @param int $id
	 * @return void
	 */
	public function __construct($id = null)
	{
		if(is_null($id)){
			$this->markNew();
		} else {
			$this->_id = (int)$id;
		}
	}
	
	/**
	 * 
	 * 
	 * @param boolean $safemode 
	 * @return array
	 */
	public function toArray($safemode = true)
	{
		return $this->_propsToArray(get_object_vars($this),$safemode);
	}
	
	/**
	 * Transforms the object into an array having property => value pairs.
	 * 
	 * @param array $properties
	 * @param boolean $safemode
	 * @return array
	 */
	protected function _propsToArray(array $properties, $safemode = true)
	{
		$array = array();
		foreach($properties as $property => $value){			
			if(!$safemode || ($safemode && !in_array($property,$this->_safemodeExclude))){
				if(is_object($value)){
					if($value instanceof GEM_Model_Domain_Object){
						$array[$property] = $value->toArray($safemode);
					} elseif(method_exists($value,'toArray')){
						$array[$property] = $value->toArray();
					}elseif(method_exists($value,'toString')){
						$array[$property] = $value->toString();
					} 
				} elseif(is_array($value)){
					$array[$property] = $this->_propsToArray($value,$safemode);
				} elseif(is_string($value) || is_int($value)){
					$array[$property] = $value;
				}
			}
		}
		return $array;
	}
	
	/**
	 * Used to limit the properties that will be displayed by the toJson() method.
	 * 
	 * @param array $properties
	 * @return void
	 */
	protected function _safemodeExclude(array $properties)
	{
		foreach($properties as $property){
			if(!property_exists($this,$property)){
				throw new GEM_Model_Domain_Exception("Property $property not found in class ".get_class($this));
			}
		}
		$this->_safemodeExclude = array_merge($properties, $this->_safemodeExclude);
	}
	
	/**
	 * Returns an array of changed properties.
	 * 
	 * @return array
	 */
	public function getChangedProperties()
	{
		return $this->_changedProperties;
	} 
	
	/**
	 * @return int
	 */
	public function getId()
	{
		return $this->_id;
	}
	
	/**
	 * @param int $id
	 * @return GEM_Model_Domain_Object | Enables a Fluent Interface
	 */
	public function setId($id)
	{
		$this->_id = $id;
		return $this;
	}
	
	/**
	 * Marks object as new.
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @return void
	 */
	public function markNew()
	{
		GEM_Model_Domain_ObjectManager::registerNew($this);	
	}
	
	/**
	 * Marks object as dirty.
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @return void
	 */
	public function markDirty()
	{
		GEM_Model_Domain_ObjectManager::registerDirty($this);
	}
	
	/**
	 * Marks object as clean.
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @return void
	 */
	public function markClean()
	{
		GEM_Model_Domain_ObjectManager::registerClean($this);
	}
	
	/**
	 * Marks object as deleted.
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @return void
	 */
	public function markDeleted()
	{
		GEM_Model_Domain_ObjectManager::registerDeleted($this);
	}
	
	/**
	 * Returns the domain object's mapper class.
	 * 
	 * @return GEM_Model_Mapper_Mapper
	 */
	public function finder()
	{ 
		return self::getFinder($this->_classType());
	}
	
	/**
	 * Retrieves the domain object's mapper class
	 * from the domain HelperFactory.
	 * 
	 * @return GEM_Model_Mapper_Mapper
	 */
	public static function getFinder($classType)
	{
		return GEM_Model_Domain_HelperFactory::getFinder($classType);
	} 
	
	/**
	 * Returns the object's class type. This is used to dynamically locate
	 * related classes.
	 * 
	 * Example: If GEM_Model_Domain_User is the object's class name,
	 * then its class type is 'User'. The {@link GEM_Model_Domain_HelperFactory}  
	 * class uses this value to locate and instantiate the domain object's mapper 
	 * and collection classes. 
	 * 
	 * @see GEM_Model_Domain_HelperFactory
	 * @return string
	 */
	protected function _classType()
	{
		$class = get_class($this);
		return substr($class,strrpos($class, "_") + 1);
	}
	
	/**
	 * Marks a property as changed. Call this method in your extending classes'
	 * setter methods to mark your properties as changed.
	 * 
	 * @param string $property
	 * @return void
	 * @throws GEM_Model_Domain_Exception
	 */
	protected function _markPropertyAsChanged($property)
	{
		$prop = "_{$property}";
		if(!property_exists(get_class($this),$prop))
			throw new GEM_Model_Domain_Exception("Could not find the property '{$property}'.");		
		if(!in_array($property,$this->_changedProperties))
			$this->_changedProperties[] = $property;
		$this->markDirty();
	}
	
	/**
	 * This magic method is called when the object is unserialized, or when it
	 * "wakes up". Objects extending this class will automatically register
	 * themselves with the Object Manager to prevent duplicate objects from
	 * being formed. If you wish to prevent the automatic registration
	 * of your object you may override this method in your objects to do
	 * absolutely nothing.
	 * 
	 * @return void
	 */
	public function __wakeup()
	{
		GEM_Model_Domain_ObjectManager::instance()->register($this);
	}
}